package timing.ukulele.common.util;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.util.StringUtils;
import timing.ukulele.common.data.PayloadData;

import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

@Slf4j
public final class JwtUtil {

    /**
     * 堆成加密
     *
     * @param algorithm     加密算法
     * @param payloadObject 待加密的内容
     * @param secret        密钥
     * @return jwt字符串
     */
    public static String encode(JWSAlgorithm algorithm, Map<String, Object> payloadObject, String secret) {
        // 1. 头部
        JWSHeader jwsHeader =
                new JWSHeader.Builder(algorithm)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();
        // 2. 载荷
        Payload payload = new Payload(payloadObject); // 这里可以传String, JSON 串，或 Map 。
        try {
            // 3. 签名器
            JWSSigner jwsSigner = new MACSigner(secret);
            // 4. 最终, JWSObject 是有状态的：未签名、已签名和签名中。很显然，在执行 .sign() 方法之后，JWSObject 对象就变成了已签名状态。
            JWSObject jwsObject = new JWSObject(jwsHeader, payload);
            // 进行签名（根据前两部分生成第三部分）
            jwsObject.sign(jwsSigner);
            return jwsObject.serialize();
        } catch (JOSEException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return null;
    }

    /**
     * 解密
     *
     * @param encode 待解密的内容
     * @return 解密后的对象
     */
    public static Map<String, Object> decode(String encode) {
        try {
            JWSObject parse = JWSObject.parse(encode);
            Payload payload = parse.getPayload();
            if (payload != null)
                return payload.toJSONObject();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 验证
     *
     * @param secret 密钥
     * @param encode 待解密的内容
     * @return 是否验证成功
     */
    public static boolean check(String secret, String encode) {
        try {
            JWSVerifier jwsVerifier = new MACVerifier(secret);
            JWSObject parse = JWSObject.parse(encode);
            return parse.verify(jwsVerifier);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * rsa非对称加密
     *
     * @param jksPath  jks密钥路径
     * @param password 密码
     * @param alias    别名
     * @return RSAKey
     */
    private static RSAKey generateRsaKey(String jksPath, String password, String alias) {
        if (StringUtils.isEmpty(jksPath) || StringUtils.isEmpty(password) || StringUtils.isEmpty(alias)) {
            return null;
        }
        // 从 classpath 下获取 RSA 秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jksPath), password.toCharArray());
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, password.toCharArray());
        // 获取 RSA 公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // 获取 RSA 私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).build();
        return rsaKey;
    }

    private static PayloadData getPayloadData(Map<String, Object> payloadContent, Integer expire) {
        Date now = new Date();
        Date exp = DateUtil.addDate(now, Calendar.SECOND, expire);
        return PayloadData.builder()
                .iat(now.getTime())
                .exp(exp.getTime())
                .payloadContent(payloadContent)
                .jti(UUID.randomUUID().toString())
                .build();

    }

    /**
     * @param jksPath       jks文件路径
     * @param password      jks密码
     * @param alias         jks别名
     * @param expire        有效期时长(秒)
     * @param payloadObject 待加密的内容
     * @return 加密后的jwt字符串
     */
    public static String rsaEncode(String jksPath, String password, String alias, Integer expire, Map<String, Object> payloadObject) {
        // JWS 头
        JWSHeader jwsHeader = new JWSHeader
                .Builder(JWSAlgorithm.RS256)    // 指定 RSA 算法
                .type(JOSEObjectType.JWT)
                .build();
        // JWS 荷载
        PayloadData payloadData = getPayloadData(payloadObject, expire);
        Payload payload = new Payload(JsonUtils.deserializer(payloadData));
        // JWS 签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        JWSSigner jwsSigner;   // rsaKey 生成签名器
        try {
            RSAKey rsaKey = generateRsaKey(jksPath, password, alias);
            if (rsaKey == null) {
                log.error("获取rsaKey失败！");
                return null;
            }
            jwsSigner = new RSASSASigner(rsaKey);
            jwsObject.sign(jwsSigner);
            // JWT/JWS 字符串
            String jwt = jwsObject.serialize();
            return jwt;
        } catch (JOSEException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return null;
    }

    /**
     * 非对称解密
     *
     * @param jksPath  jks路径
     * @param password 密码
     * @param alias    别名
     * @param token    jwt字符串
     * @return 解密后的json对象
     */
    public static Map<String, Object> rsaDecode(String jksPath, String password, String alias, String token) {
        try {
            // JWT/JWS 字符串转 JWSObject 对象
            JWSObject jwsObject = JWSObject.parse(token);
            // 根据公要生成验证器
            RSAKey rsaKey = generateRsaKey(jksPath, password, alias);
            if (rsaKey == null) {
                log.error("获取rsaKey失败！");
                return null;
            }
            RSAKey publicRsaKey = rsaKey.toPublicJWK();
            JWSVerifier jwsVerifier = new RSASSAVerifier(publicRsaKey);
            // 使用校验器校验 JWSObject 对象的合法性
            if (!jwsObject.verify(jwsVerifier)) {
                log.error("token签名合法");
                return null;
            }
            // 拆解 JWT/JWS，获得荷载中的内容
            Payload payload = jwsObject.getPayload();
            return payload.toJSONObject();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return null;
    }

}